﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;

namespace Zinnia.Caching.Imp
{
    /// <summary>
    /// 缓存对象
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    /// <typeparam name="TResult"></typeparam>
    public class Cache<TKey, TResult> : ICache<TKey, TResult>
    {
        private readonly ICacheContextAccessor _cacheContextAccessor;
        /// <summary>
        /// 线程安全集合类
        /// </summary>
        private readonly ConcurrentDictionary<TKey, CacheEntry> _entries;
        public Cache(ICacheContextAccessor cacheContextAccessor)
        {
            _cacheContextAccessor = cacheContextAccessor;
            _entries = new ConcurrentDictionary<TKey, CacheEntry>();
        }
        public TResult Get(TKey key, Func<AcquireContext<TKey>, TResult> acquire)
        {
            var entry = _entries.AddOrUpdate(key,
                //Add lambda
                k => AddEntry(k, acquire),
                //Update lambda
                (k, currentEntry) => UpdateEntry(currentEntry, k, acquire)
                );
            return entry.Result;
        }
        /// <summary>
        /// 添加
        /// </summary>
        /// <param name="k"></param>
        /// <param name="acquire"></param>
        /// <returns></returns>
        private CacheEntry AddEntry(TKey k, Func<AcquireContext<TKey>, TResult> acquire)
        {
            var entry = CreateEntry(k, acquire);
            PropagateTokens(entry);
            return entry;
        }
        /// <summary>
        /// 更新
        /// </summary>
        /// <param name="currentEntry"></param>
        /// <param name="k"></param>
        /// <param name="acquire"></param>
        /// <returns></returns>
        private CacheEntry UpdateEntry(CacheEntry currentEntry, TKey k, Func<AcquireContext<TKey>, TResult> acquire)
        {
            var entry = (currentEntry.Tokens.Any((t => null != t && t.IsCurrent)) ? CreateEntry(k, acquire) : currentEntry);
            PropagateTokens(entry);
            return entry;
        }
        /// <summary>
        /// 传递Tokens
        /// </summary>
        /// <param name="entry"></param>
        private void PropagateTokens(CacheEntry entry)
        {
            if (null != _cacheContextAccessor.Current)
            {
                foreach (var token in entry.Tokens)
                {
                    _cacheContextAccessor.Current.Monitor(token);
                }
            }
        }

        /// <summary>
        /// 创建缓存入口对象
        /// </summary>
        /// <param name="k"></param>
        /// <param name="acquire"></param>
        /// <returns></returns>
        private CacheEntry CreateEntry(TKey k, Func<AcquireContext<TKey>, TResult> acquire)
        {
            var entry = new CacheEntry();
            var context = new AcquireContext<TKey>(k, entry.AddToken);
            IAcquireContext parentContext = null;
            try
            {
                //push context
                parentContext = _cacheContextAccessor.Current;
                _cacheContextAccessor.Current = context;
                entry.Result = acquire(context);
            }
            finally
            {
                //pop context
                _cacheContextAccessor.Current = parentContext;
            }
            return entry;
        }

        /// <summary>
        /// 缓存入口
        /// </summary>
        class CacheEntry
        {
            private IList<IVolatileToken> _tokens;

            public TResult Result { get; set; }
            /// <summary>
            /// 令牌集合
            /// </summary>
            public IEnumerable<IVolatileToken> Tokens
            {
                get { return _tokens ?? Enumerable.Empty<IVolatileToken>(); }
            }
            /// <summary>
            /// 添加Tokens
            /// </summary>
            /// <param name="volatileToken"></param>
            public void AddToken(IVolatileToken volatileToken)
            {
                if (null == _tokens)
                {
                    _tokens = new List<IVolatileToken>();
                }
                _tokens.Add(volatileToken);
            }
            /// <summary>
            /// 压缩Tokens,去除重复部分
            /// </summary>
            public void CompactTokens()
            {
                if (null != _tokens)
                {
                    //去重
                    _tokens = _tokens.Distinct().ToArray();
                }
            }

        }
    }
}
